package cn.itsource.ymcc.service.impl;

import cn.itsource.ymcc.constant.ErrorConstantCode;
import cn.itsource.ymcc.domain.AlipayInfo;
import cn.itsource.ymcc.domain.PayFlow;
import cn.itsource.ymcc.domain.PayOrder;
import cn.itsource.ymcc.dto.AlipayNotifyDto;
import cn.itsource.ymcc.dto.NotifyPayMsgDto;
import cn.itsource.ymcc.dto.PayDto;
import cn.itsource.ymcc.exception.GlobalException;
import cn.itsource.ymcc.mapper.PayOrderMapper;
import cn.itsource.ymcc.result.JSONResult;
import cn.itsource.ymcc.service.IAlipayInfoService;
import cn.itsource.ymcc.service.IPayFlowService;
import cn.itsource.ymcc.service.IPayOrderService;
import cn.itsource.ymcc.util.AssertUtil;
import cn.itsource.ymcc.util.StrUtils;
import com.alibaba.fastjson.JSON;
import com.alipay.easysdk.factory.Factory;
import com.alipay.easysdk.kernel.Config;
import com.alipay.easysdk.kernel.util.ResponseChecker;
import com.alipay.easysdk.payment.common.models.AlipayTradeCancelResponse;
import com.alipay.easysdk.payment.common.models.AlipayTradeCloseResponse;
import com.alipay.easysdk.payment.page.models.AlipayTradePagePayResponse;
import com.baomidou.mybatisplus.mapper.EntityWrapper;
import com.baomidou.mybatisplus.mapper.Wrapper;
import com.baomidou.mybatisplus.service.impl.ServiceImpl;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.client.producer.SendStatus;
import org.apache.rocketmq.client.producer.TransactionSendResult;
import org.apache.rocketmq.spring.core.RocketMQTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.Message;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

import static cn.itsource.ymcc.constant.MQConstants.*;

/**
 * <p>
 *  服务实现类
 * </p>
 *
 * @author gwk
 * @since 2023-03-14
 */
@Service
@Slf4j
public class PayOrderServiceImpl extends ServiceImpl<PayOrderMapper, PayOrder> implements IPayOrderService {

    @Autowired
    private IAlipayInfoService alipayInfoService;

    @Autowired
    private IPayFlowService payFlowService;

    @Autowired
    private RocketMQTemplate rocketMQTemplate;

    @Override
    public JSONResult checkPayOrder(String orderNO) {
        AssertUtil.isNotNull(orderNO, ErrorConstantCode.PARAMETER_NOT_BE_NULL);

        PayOrder payOrder = getPayOrder(orderNO);

        // 如果支付单存在，就返回success
        return payOrder==null?JSONResult.error():JSONResult.success();

    }



    @Override
    public String apply(PayDto payDto) {
        log.info("前端发起支付请求{}",payDto);
        // 	1. 校验参数 - JSR303
        String callUrl = payDto.getCallUrl();
        String orderNo = payDto.getOrderNo();
        Integer payType = payDto.getPayType();

        //	2. 业务校验  orderNo 查询支付单
        PayOrder payOrder = getPayOrder(orderNo);
        AssertUtil.isNotNull(payOrder,ErrorConstantCode.PARAMETER_NOT_BE_NULL);

        //	3. 根据支付类型，向对应的三方平台发起支付。
        String html = "";
        if(payType.equals(PayOrder.ZFB)){
            // 调用支付宝支付
           html =  payOrderByZFB(payOrder,callUrl);

            //返回 支付宝响应的支付页面，
            //return html;
        }

        if(payType.equals(PayOrder.WEICHAT)){
            // 调用微信支付
            //return null;
        }

        // 4. 需要发送延迟消息到MQ，避免超时未支付！
        rocketMQTemplate.syncSend(DELAY_ORDER_NOTIFY_TOPIC+":"+"tags",MessageBuilder.withPayload(orderNo).build()
                ,3000,4);//  延迟30秒发送延迟消息
        log.info("发送延迟消息，到消息队列！！");

        return html;
    }

    /**
     * 参数校验
     * @param alipayNotifyDto
     * @return
     */
    @Override
    public String alipayNotify(AlipayNotifyDto alipayNotifyDto) {
        //1. 校验参数 -- jsr303
        //2. 签名校验 RSA 加密
        Map<String, String> parameters = JSON.parseObject(JSON.toJSONString(alipayNotifyDto),Map.class);
        try {
            Boolean verify = Factory.Payment.Common().verifyNotify(parameters);
            if(!verify){
                return "error";
            }
        } catch (Exception e) {
            e.printStackTrace();
            return "error";
        }
        //3. 订单信息校验，根据支付单号查询金额，商品，
        log.info("校验订单信息{}",alipayNotifyDto);
        String trade_no = alipayNotifyDto.getOut_trade_no();
        PayOrder payOrder = getPayOrder(trade_no);
        if(payOrder==null){
            return "error";
        }


        String total_amount = alipayNotifyDto.getTotal_amount();
        if(!(payOrder.getAmount().compareTo(new BigDecimal(total_amount))==0)){
            log.info("支付金额 {}",alipayNotifyDto.getTotal_amount());
            return "error";
        };
        //购买课程 [java大神班;] , 支付[222.00] 元
        if(!payOrder.getSubject().equals(alipayNotifyDto.getSubject())){
            log.info("订单标题 {}",alipayNotifyDto.getSubject());
            return "error";
        }

        AlipayInfo alipayInfo = alipayInfoService.selectList(null).get(0);
        if(!alipayInfo.getAppId().equals(alipayNotifyDto.getAuth_app_id())){
            log.info("appid {}",alipayNotifyDto.getSubject());
            return "error";
        }

        if(alipayNotifyDto.isTradeFail()){
            log.info("订单标题交易失败 {}");
            return "error";
        }

        if(alipayNotifyDto.isTradeWit()){
            log.info("用户还为支付 {}");
            return "wait";
        }

        if(alipayNotifyDto.isTradeSuccess()){
            // 分布式事务 -- MQ事务消息
            NotifyPayMsgDto  msgDto = new NotifyPayMsgDto();
            msgDto.setOrderNo(trade_no);

            Map<String, Object> extParams = StrUtils.passbackParams2Map(payOrder.getExtParams());
            msgDto.setCourseIds(extParams.get("relationId").toString());

            msgDto.setPayStatus(PayOrder.OK_PAY);
            msgDto.setUserId(payOrder.getUserId());

            String jsonMsgDto = JSON.toJSONString(msgDto);

            Message<String> build = MessageBuilder.withPayload(jsonMsgDto).build();

            TransactionSendResult result = rocketMQTemplate.sendMessageInTransaction(TX_PAY_NOTIFY_PRODUCE_GROUP,
                    "pay-notify-topic:tx-tags", build, trade_no);

            if(!(result.getSendStatus().equals(SendStatus.SEND_OK))){
                log.info("MQ消息的发送失败！ {}");
                return "error";
            }
            //4. 修改支付单状态
            //5. 保存支付流水
            //6. 修改商品订单状态
            //7. 保存用户和商品的信息
            return "success";
        }


        return "other";
    }

    /**
     *  修改支付单状态
     *  保存支付流水
     * @param orderNo
     */
    @Override
    @Transactional(propagation = Propagation.REQUIRED,readOnly = false)
    public void updatePayOrderAndSaveFlow(String orderNo) {
        AssertUtil.isNotNull(orderNo,ErrorConstantCode.PARAMETER_NOT_BE_NULL);

        PayOrder payOrder = getPayOrder(orderNo);
        AssertUtil.isNotNull(payOrder,ErrorConstantCode.PARAMETER_NOT_BE_NULL);
        //4. 修改支付单状态
        Date date = new Date();
        payOrder.setPayStatus(PayOrder.OK_PAY);
        payOrder.setUpdateTime(date);
        updateById(payOrder);

        //5. 保存支付流水
        PayFlow payFlow = new PayFlow();
        payFlow.setNotifyTime(date);
        payFlow.setSubject(payOrder.getSubject());
        payFlow.setOutTradeNo(payOrder.getOrderNo());
        payFlow.setTotalAmount(payOrder.getAmount());
        payFlow.setTradeStatus(AlipayNotifyDto.TRADE_SUCCESS);
        payFlow.setPassbackParams(payOrder.getExtParams());
        payFlow.setPaySuccess(true);
        payFlowService.insert(payFlow);


    }

    private String payOrderByZFB(PayOrder payOrder, String callUrl) {
        log.info("支付宝支付{}",payOrder);
        AlipayInfo alipayInfo = alipayInfoService.selectList(null).get(0);
        // 1. 设置参数（全局只需设置一次）
        Factory.setOptions(getOptions(alipayInfo));
        try {
            // 2. 发起API调用（以创建当面付收款二维码为例）
            AlipayTradePagePayResponse response = Factory.Payment.Page().pay(payOrder.getSubject(),payOrder.getOrderNo(),
                    payOrder.getAmount().toString(),callUrl );

            // 3. 处理响应或异常
            if (ResponseChecker.success(response)) {
                log.info("调用成功,支付宝支付的结果：{}",response.getBody());
                return response.getBody();
            } else {
                System.err.println("调用失败，原因：" + response.getBody());
                throw new GlobalException("支付调用失败！"+response.getBody());
            }
        } catch (Exception e) {
            System.err.println("调用遭遇异常，原因：" + e.getMessage());
            throw new GlobalException("支付调用失败！"+e.getMessage());
        }

    }

    private static Config getOptions(AlipayInfo alipayInfo) {

        Config config = new Config();
        config.protocol = alipayInfo.getProtocol();
        config.gatewayHost = alipayInfo.getGatewayHost();
        config.signType = alipayInfo.getSignType();
        config.appId = alipayInfo.getAppId();
        // 为避免私钥随源码泄露，推荐从文件中读取私钥字符串而不是写入源码中
        config.merchantPrivateKey = alipayInfo.getMerchantPrivateKey();
        //注：证书文件路径支持设置为文件系统中的路径或CLASS_PATH中的路径，优先从文件系统中加载，加载失败后会继续尝试从CLASS_PATH中加载
//        config.merchantCertPath = "<-- 请填写您的应用公钥证书文件路径，例如：/foo/appCertPublicKey_2019051064521003.crt -->";
//        config.alipayCertPath = "<-- 请填写您的支付宝公钥证书文件路径，例如：/foo/alipayCertPublicKey_RSA2.crt -->";
//        config.alipayRootCertPath = "<-- 请填写您的支付宝根证书文件路径，例如：/foo/alipayRootCert.crt -->";
        //注：如果采用非证书模式，则无需赋值上面的三个证书路径，改为赋值如下的支付宝公钥字符串即可
        config.alipayPublicKey = alipayInfo.getAlipayPublicKey();
        //可设置异步通知接收服务地址（可选）
        config.notifyUrl = alipayInfo.getNotifyUrl();
        //可设置AES密钥，调用AES加解密相关接口时需要（可选）
        //config.encryptKey = "<-- 请填写您的AES密钥，例如：aa4BtZ4tspm2wnXLb1ThQA== -->";
        return config;
    }

    /**
     * 根据订单号，查询支付单信息
     * @param orderNO
     * @return
     */
    public PayOrder getPayOrder(String orderNO) {
        // 根据订单编号，查询支付单
        Wrapper<PayOrder> wrapper = new EntityWrapper<>();
        wrapper.eq("order_no", orderNO);
        PayOrder payOrder = selectOne(wrapper);
        return payOrder;
    }

    @Override
    public void alipayClose(String orderNo) {
        try {
            AlipayTradeCancelResponse cancel = Factory.Payment.Common().cancel(orderNo);
            AlipayTradeCloseResponse close = Factory.Payment.Common().close(orderNo);
            log.info("调用支付宝，cancel关单成功{}，{}",cancel.httpBody,cancel.msg);
            log.info("调用支付宝，close关单成功{}，{}",close.httpBody,close.msg);
        } catch (Exception e) {
            log.error("调用支付宝，关单失败{}",orderNo);
            e.printStackTrace();
        }
    }
}
